#include "Rp2ShaderEffect.h"
#include "Rp2Geometry.h"
#include "Rp2Catalog.h"
#include "Rp2Renderer.h"

using namespace Rp2;

RP2_IMPLEMENT_RTTI(Rp2, ShaderEffect, Effect);

//---------------------------------------------------------------------------------------------------
ShaderEffect::ShaderEffect(int iPassQuantity)
{
	assert(iPassQuantity > 0);
	SetPassQuantity(iPassQuantity);
}
//---------------------------------------------------------------------------------------------------
ShaderEffect::ShaderEffect()
{
	m_iPassQuantity = 0;
}
//---------------------------------------------------------------------------------------------------
ShaderEffect::~ShaderEffect()
{
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::SetPassQuantity(int iPassQuantity)
{
	assert(iPassQuantity > 0);
	m_iPassQuantity = iPassQuantity;

    m_kVShader.resize(m_iPassQuantity);
    m_kPShader.resize(m_iPassQuantity);
    m_kAlphaState.resize(m_iPassQuantity);
    m_kVConnect.resize(m_iPassQuantity);
    m_kPConnect.resize(m_iPassQuantity);
	SetDefaultAlphaState();
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::SetDefaultAlphaState()
{
    // Create default alpha states. The Renderer enables this on a multieffect
    // drawing operation.  The first pass uses the default alpha state
    // (SBF_SRC_ALPHA, DBF_ONE_MINUS_SRC_ALPHA).  All other passes use
    // modulation and all are enabled.  These may be overridden by your
    // application code by accessing the state via effect->GetBlending(pass).
	m_kAlphaState[0] = new AlphaState;
	m_kAlphaState[0]->BlendEnabled = true;
	for (int i = 1; i < (int)m_kAlphaState.size(); i++)
	{
		m_kAlphaState[i] = new AlphaState;
		m_kAlphaState[i]->BlendEnabled = true;
		m_kAlphaState[i]->SrcBlend = AlphaState::SBF_DST_COLOR;
		m_kAlphaState[i]->DstBlend = AlphaState::DBF_ZERO;
	}
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::SetGlobalState(int iPass, Renderer* pkRenderer, bool bPrimaryEffect)
{
	assert (0 <= iPass && iPass < m_iPassQuantity && pkRenderer);

    // Enable all passes after the first one to blend with the previous
    // passes.
	if (!bPrimaryEffect || iPass > 0)
	{
		m_kAlphaState[iPass]->BlendEnabled = true;
		
		AlphaStatePtr spkSave = pkRenderer->GetAlphaState();
		pkRenderer->SetAlphaState(m_kAlphaState[iPass]);
		m_kAlphaState[iPass] = spkSave;
	}
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::RestoreGlobalState(int iPass, Renderer* pkRenderer, bool bPrimaryEffect)
{
    assert(0 <= iPass && iPass < m_iPassQuantity && pkRenderer);

	if (!bPrimaryEffect || iPass > 0)
	{
		AlphaStatePtr spkSave = pkRenderer->GetAlphaState();
		pkRenderer->SetAlphaState(m_kAlphaState[iPass]);
		m_kAlphaState[iPass] = spkSave;
	}
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::ConnectVShaderConstant(int iPass, const std::string& rkName, float* pfSource)
{
	UserConstant* pkUC = GetVConstant(iPass, rkName);
	m_kVConnect[iPass].push_back(std::make_pair(pkUC, pfSource));
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::ConnectPShaderConstant(int iPass, const std::string& rkName, float* pfSource)
{
	UserConstant* pkUC = GetPConstant(iPass, rkName);
	m_kPConnect[iPass].push_back(std::make_pair(pkUC, pfSource));
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::ConnectVShaderConstants(int iPass)
{
	if (0 <= iPass && iPass < (int)m_kVConnect.size())
	{
		for (int i = 0; i < (int)m_kVConnect[iPass].size(); i++)
		{
			UserConstant* pkUC = m_kVConnect[iPass][i].first;
			float* pfSource = m_kVConnect[iPass][i].second;
			pkUC->SetDataSource(pfSource);
		}
	}
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::ConnectPShaderConstants(int iPass)
{
    if (0 <= iPass && iPass < (int)m_kPConnect.size())
    {
        for (int i = 0; i < (int)m_kPConnect[iPass].size(); i++)
        {
            UserConstant* pkUC = m_kPConnect[iPass][i].first;
            float* pfSource = m_kPConnect[iPass][i].second;
            pkUC->SetDataSource(pfSource);
        }
    }
}
//---------------------------------------------------------------------------------------------------
bool ShaderEffect::SetVShader (int iPass, VertexShader* pkVShader)
{
    assert(0 <= iPass && iPass < m_iPassQuantity);
    m_kVShader[iPass] = pkVShader;

    if (m_kVShader[iPass] && m_kPShader[iPass])
    {
        VertexProgram* pkVProgram = m_kVShader[iPass]->GetProgram();
        PixelProgram* pkPProgram = m_kPShader[iPass]->GetProgram();
        return AreCompatible(pkVProgram,pkPProgram);
    }

    return true;
}
//---------------------------------------------------------------------------------------------------
bool ShaderEffect::SetPShader (int iPass, PixelShader* pkPShader)
{
    assert(0 <= iPass && iPass < m_iPassQuantity);
    m_kPShader[iPass] = pkPShader;

    if (m_kVShader[iPass] && m_kPShader[iPass])
    {
        VertexProgram* pkVProgram = m_kVShader[iPass]->GetProgram();
        PixelProgram* pkPProgram = m_kPShader[iPass]->GetProgram();
        return AreCompatible(pkVProgram,pkPProgram);
    }

    return true;
}
//---------------------------------------------------------------------------------------------------
bool ShaderEffect::AreCompatible (const VertexProgram* pkVProgram,
    const PixelProgram* pkPProgram) const
{
    if ((pkVProgram && !pkPProgram) || (!pkVProgram && pkPProgram))
    {
        return true;
    }

    // Ensure that the output of the vertex program and the input of the
    // pixel program are compatible.  Each vertex program always has a clip
    // position output.  This is not relevant to the compatibility check.
    const Attributes& rkVOAttr = pkVProgram->GetOutputAttributes();
    const Attributes& rkPIAttr = pkPProgram->GetInputAttributes();
    return rkVOAttr.Matches(rkPIAttr,false,true,true,true,true,true,true,
        true,true,true);
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::LoadResources (Renderer* pkRenderer, Geometry* pkGeometry)
{
    for (int iPass = 0; iPass < m_iPassQuantity; iPass++)
    {
        // Load the programs into video memory.
        VertexProgram* pkVProgram = 0;
        if (m_kVShader[iPass])
        {
            pkVProgram = m_kVShader[iPass]->GetProgram();
            pkRenderer->LoadVProgram(pkVProgram);
        }

        if (m_kPShader[iPass])
        {
            pkRenderer->LoadPProgram(m_kPShader[iPass]->GetProgram());
        }

        // Load the textures into video memory.
        const int iPTQuantity = GetPTextureQuantity(iPass);
        for (int i = 0; i < iPTQuantity; i++)
        {
            pkRenderer->LoadTexture(m_kPShader[iPass]->GetTexture(i));
        }

        if (pkGeometry && pkVProgram)
        {
            // Load the vertex buffer into video memory.
            const Attributes& rkIAttr = pkVProgram->GetInputAttributes();
            const Attributes& rkOAttr = pkVProgram->GetOutputAttributes();
            pkRenderer->LoadVBuffer(rkIAttr,rkOAttr,pkGeometry->VBuffer);

            // Load the index buffer into video memory.
            pkRenderer->LoadIBuffer(pkGeometry->IBuffer);
        }
    }
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::ReleaseResources (Renderer* pkRenderer,
    Geometry* pkGeometry)
{
    for (int iPass = 0; iPass < m_iPassQuantity; iPass++)
    {
        // Release the programs from video memory.
        if (m_kVShader[iPass])
        {
            pkRenderer->ReleaseVProgram(m_kVShader[iPass]->GetProgram());
        }

        if (m_kPShader[iPass])
        {
            pkRenderer->ReleasePProgram(m_kPShader[iPass]->GetProgram());
        }

        // Release the textures from video memory.
        const int iPTQuantity = GetPTextureQuantity(iPass);
        for (int i = 0; i < iPTQuantity; i++)
        {
            pkRenderer->ReleaseTexture(m_kPShader[iPass]->GetTexture(i));
        }

        if (pkGeometry)
        {
            // Release the vertex buffer from video memory.
            pkRenderer->ReleaseVBuffer(pkGeometry->VBuffer);

            // Release the index buffer from video memory.
            pkRenderer->ReleaseIBuffer(pkGeometry->IBuffer);
        }
    }
}
//---------------------------------------------------------------------------------------------------
// name-id system
//---------------------------------------------------------------------------------------------------
Object* ShaderEffect::GetObjectByName (const std::string& rkName)
{
    Object* pkFound = Effect::GetObjectByName(rkName);
    if (pkFound)
    {
        return pkFound;
    }

    for (int iPass = 0; iPass < m_iPassQuantity; iPass++)
    {
        if (m_kVShader[iPass])
        {
            pkFound = m_kVShader[iPass]->GetObjectByName(rkName);
            if (pkFound)
            {
                return pkFound;
            }
        }

        if (m_kPShader[iPass])
        {
            pkFound = m_kPShader[iPass]->GetObjectByName(rkName);
            if (pkFound)
            {
                return pkFound;
            }
        }
    }

    return 0;
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::GetAllObjectsByName (const std::string& rkName,
    std::vector<Object*>& rkObjects)
{
    Effect::GetAllObjectsByName(rkName,rkObjects);

    for (int iPass = 0; iPass < m_iPassQuantity; iPass++)
    {
        if (m_kVShader[iPass])
        {
            m_kVShader[iPass]->GetAllObjectsByName(rkName,rkObjects);
        }

        if (m_kPShader[iPass])
        {
            m_kPShader[iPass]->GetAllObjectsByName(rkName,rkObjects);
        }
    }
}
//---------------------------------------------------------------------------------------------------
Object* ShaderEffect::GetObjectByID (unsigned int uiID)
{
    Object* pkFound = Effect::GetObjectByID(uiID);
    if (pkFound)
    {
        return pkFound;
    }

    for (int iPass = 0; iPass < m_iPassQuantity; iPass++)
    {
        if (m_kVShader[iPass])
        {
            pkFound = m_kVShader[iPass]->GetObjectByID(uiID);
            if (pkFound)
            {
                return pkFound;
            }
        }

        if (m_kPShader[iPass])
        {
            pkFound = m_kPShader[iPass]->GetObjectByID(uiID);
            if (pkFound)
            {
                return pkFound;
            }
        }
    }

    return 0;
}
//---------------------------------------------------------------------------------------------------
/*
void ShaderEffect::EnableProgram()
{
	if (!m_iProgramID)
	{
		// load shader program
		Shader* pkShader = new Shader;
		switch(m_eEffect)
		{
			case SE_TEXTURE:
			{
				m_iProgramID = pkShader->CreateProgram("Texture.vp", "Texture.fp", 2,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_TCOORD_2, "kTexture0");
				break;				
			}
			case SE_DEFAULT:
			{
				m_iProgramID = pkShader->CreateProgram("Shader.vp", "Shader.fp", 3,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_NORMAL, "kNormal",
					Attributes::ATB_TCOORD_2, "kTexture0");
				break;
			}
			case SE_ADSLIGHT:
			{
				m_iProgramID = pkShader->CreateProgram("Shader.vp", "Shader.fp", 3,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_NORMAL, "kNormal",
					Attributes::ATB_TCOORD_2, "kTexture0");

				break;
			}
			case SE_ORTHO:
			{
				m_iProgramID = pkShader->CreateProgram("Ortho.vp", "Ortho.fp", 2,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_TCOORD_2, "kTexCoords");

				break;
			}
			case SE_CUBEMAP:
			{
				m_iProgramID = pkShader->CreateProgram("CubeMap.vp", "CubeMap.fp", 2,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_NORMAL, "kNormal");
				break;
			}
			case SE_CUBEREFLECT:
			{
				m_iProgramID = pkShader->CreateProgram("CubeReflect.vp", "CubeReflect.fp",2,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_NORMAL, "kNormal");
				break;
			}
			case SE_MULTIEFFECT:
			{
				m_iProgramID = pkShader->CreateProgram("MultiTexture.vp", "MultiTexture.fp",2,
					Attributes::ATB_VERTEX_3, "kVertex", Attributes::ATB_TCOORD_2, "kTexture0");
				break;				
			}
			default:
				assert(false);
		}

	}

	// enable the program
	if (!m_iProgramID)
	{
		assert(false);
	}
	glUseProgram((GLuint)m_iProgramID);

	
}
//---------------------------------------------------------------------------------------------------
void ShaderEffect::SetUniforms(Renderer* pkRenderer)
{
	switch(m_eEffect)
	{
		case SE_TEXTURE:
		{
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");	
			unsigned int uiLocTexture = glGetUniformLocation(m_iProgramID, "kColorMap");
		
			// Uniforms			
			// update mvp matrix for world transform
			Matrix4f mvpM = pkRenderer->GetViewProjectionMatrix();
			mvpM = pkRenderer->GetWorldMatrix() * mvpM;
			
			glUniform1i(uiLocTexture, 0);
			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)mvpM);

			break;
		}
		case SE_ADSLIGHT:
		{
			// uniforms
			unsigned int uiLocAmbientColor = glGetUniformLocation(m_iProgramID, "kAmbientColor");
			unsigned int uiLocDiffuseColor = glGetUniformLocation(m_iProgramID, "kDiffuseColor");
			unsigned int uiLocSpecularColor = glGetUniformLocation(m_iProgramID, "kSpecularColor");
			unsigned int uiLocWorldLightPos = glGetUniformLocation(m_iProgramID, "kWorldLightPos");
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");
			unsigned int uiLocMvMatrix = glGetUniformLocation(m_iProgramID, "kMvMatrix");
			unsigned int uiLocVMatrix = glGetUniformLocation(m_iProgramID, "kNormalMatrix");
			unsigned int uiLocTexture = glGetUniformLocation(m_iProgramID, "kColorMap");					
			
			// Uniforms
			Vector4f kWorldLightPos(-100.0f, 100.0f, -10.0f, 1.0f);
			Vector4f kAmbientColor(0.7f, 0.7f, 0.7f, 1.0f);
			Vector4f kDiffColor(1.0f, 1.0f, 1.0f, 1.0f);
			Vector4f kSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);

			Matrix4f mvpM = pkRenderer->GetViewProjectionMatrix();
			Matrix4f mvM = pkRenderer->GetViewMatrix();
			Matrix3f vM = pkRenderer->GetNormalMatrix();

			// update mvp matrix for world transform
			mvpM = pkRenderer->GetWorldMatrix() * mvpM;

			glUniform1i(uiLocTexture, 0);
			glUniform4fv(uiLocAmbientColor, 1, (GLfloat*)(float*)kAmbientColor);
			glUniform4fv(uiLocDiffuseColor, 1, (GLfloat*)(float*)kDiffColor);
			glUniform4fv(uiLocWorldLightPos, 1, (GLfloat*)(float*)kWorldLightPos);
			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)mvpM);
			glUniformMatrix4fv(uiLocMvMatrix, 1, GL_TRUE, (GLfloat*)(float*)mvM);
			glUniformMatrix3fv(uiLocVMatrix, 1, GL_TRUE, (GLfloat*)(float*)vM);

			break;
		}
		case SE_ORTHO:
		{
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");
			unsigned int uiLocRectTex = glGetUniformLocation(m_iProgramID, "kRectImage");

			Matrix4f kMvpM = pkRenderer->GetOrthoMatrix();

			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)kMvpM);
			glUniform1i(uiLocRectTex, 0);
			
			break;
		}
		case SE_CUBEMAP:
		{
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");
			unsigned int uiLocCubeTex = glGetUniformLocation(m_iProgramID, "kCubeMap");

			Matrix4f kMvpM = pkRenderer->GetViewProjectionMatrix();

			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)kMvpM);
			glUniform1i(uiLocCubeTex, 0);

			break;
		}
		case SE_CUBEREFLECT:
		{
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");
			unsigned int uiLocMvMatrix = glGetUniformLocation(m_iProgramID, "kMvMatrix");
			unsigned int uiLocNMatrix = glGetUniformLocation(m_iProgramID, "kNormalMatrix");
			unsigned int uiLocInvVMatrix = glGetUniformLocation(m_iProgramID, "kInvViewMatrix");

			Matrix4f kMvpM = pkRenderer->GetViewProjectionMatrix();
			Matrix4f kMvM = pkRenderer->GetViewMatrix();
			Matrix3f kNM = pkRenderer->GetNormalMatrix();
			Matrix4f kInvVM;
			kInvVM.MakeHomogeneous(kNM);
			kInvVM = kInvVM.Inverse();

			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)kMvpM);
			glUniformMatrix4fv(uiLocMvMatrix, 1, GL_TRUE, (GLfloat*)(float*)kMvM);
			glUniformMatrix3fv(uiLocNMatrix, 1, GL_TRUE, (GLfloat*)(float*)kNM);
			glUniformMatrix4fv(uiLocInvVMatrix, 1, GL_TRUE, (GLfloat*)(float*)kInvVM);

			break;
		}
		case SE_MULTIEFFECT:
		{
			// uniforms
			unsigned int uiLocMvpMatrix = glGetUniformLocation(m_iProgramID, "kMvpMatrix");
			unsigned int uiLocTexture = glGetUniformLocation(m_iProgramID, "kColorMap");			
			unsigned int uiLocTexture2 = glGetUniformLocation(m_iProgramID, "kColorMap2");			
			
			Matrix4f mvpM = pkRenderer->GetViewProjectionMatrix();

			// update mvp matrix for world transform
			mvpM = pkRenderer->GetWorldMatrix() * mvpM;

			glUniform1i(uiLocTexture, 0);
			glUniform1i(uiLocTexture2, 1);
			glUniformMatrix4fv(uiLocMvpMatrix, 1, GL_TRUE, (GLfloat*)(float*)mvpM);

			break;
		}

	}

}
//---------------------------------------------------------------------------------------------------
*/